﻿#region Copyright information
// 
// Copyright © 2012-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the SubnetworkToolkit and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: GeneticSearch.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@outlook.com
// - Date Created: 2013-01-30 2:55 PM
// - Last Modified: 2013-01-30 3:21 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BOL.Algorithms.Optimization.EvolutionaryAlgorithms;
using BOL.Linq.Descriptive;

namespace SubnetworkToolkit.GeneticAlgorithmSearch
{
    public class GeneticSearch : GeneticAlgorithmBase<int, Network>
    {
        #region Private variables

        private int _s;
        private readonly int _minNodes;
        private readonly IDictionary<int, List<int>> _dictionary;
        private readonly IDictionary<int, double> _statistics;
        private readonly IList<int> _bypass;
        private readonly bool _forcing;
        private double _prevFitnessNorm;

        #endregion

        #region Public properties

        public int StationarySolution { get; private set; }

        #endregion

        #region Constructor

        public GeneticSearch(Random random, IDictionary<int, List<int>> dictionary, IDictionary<int, double> statistics, int minNodes, int maxNodes, int populationSize, int numberOfElites, SelectionMethod selectionMethod, double crossoverRate, double mutationRate, int stationarySolution, double tolerance, bool allowRedundancy, bool isVerbose, bool doParallel, IList<int> bypass, double mean, double stdDev, bool forcing = false)
            : base(
            random,
            new SubnetGenome(
                random, 
                dictionary,
                statistics,
                new List<int>(),
                minNodes,
                maxNodes,
                bypass,
                mean,
                stdDev
                ),
            populationSize,
            numberOfElites,
            selectionMethod,
            crossoverRate,
            mutationRate,
            -1,
            allowRedundancy,
            tolerance,
            isVerbose,
            doParallel)
        {
            if (dictionary == null)
                throw new ArgumentNullException("dictionary");
            if (statistics == null)
                throw new ArgumentNullException("statistics");

            _minNodes = minNodes;
            _dictionary = dictionary;
            _statistics = statistics;
            _s = 0;
            StationarySolution = stationarySolution;
            _bypass = bypass;
            _forcing = forcing;
            _prevFitnessNorm = 1;
        }

        public GeneticSearch(Random random, IDictionary<int, List<int>> dictionary, IDictionary<int, double> statistics, int minNodes, int maxNodes, int populationSize, int numberOfElites, int k, double crossoverRate, double mutationRate, int stationarySolution, double tolerance, bool allowRedundancy, bool isVerbose, bool doParallel, IList<int> bypass, double mean, double stdDev, bool forcing = false)
            : base(
            random,
            new SubnetGenome(
                random,
                dictionary,
                statistics,
                new List<int>(),
                minNodes,
                maxNodes,
                bypass,
                mean,
                stdDev
                ),
            populationSize,
            numberOfElites,
            k,
            crossoverRate,
            mutationRate,
            -1,
            allowRedundancy,
            tolerance,
            isVerbose,
            doParallel)
        {
            if (dictionary == null)
                throw new ArgumentNullException("dictionary");
            if (statistics == null)
                throw new ArgumentNullException("statistics");

            _dictionary = dictionary;
            _statistics = statistics;
            _s = 0;
            StationarySolution = stationarySolution;
            _bypass = bypass;
            _forcing = forcing;
            _prevFitnessNorm = 1;
        }

        protected override void InitializePopulation()
        {
            if (IsVerbose)
                Console.WriteLine("\n[Initialization]");

            var i = 0;
            var enumerator = _dictionary.ToDictionary(x => x.Key, x => _statistics[x.Key]).
                OrderByDescending(x => Math.Abs(x.Value)).
                Select(x => x.Key).GetEnumerator();

            // Initializes current population
            Population = new List<IGenome<int>>();
            while (i < PopulationSize)
            {
                if (!enumerator.MoveNext())
                    enumerator.Reset();

                Population.Add(((SubnetGenome)Seed).Offspring(enumerator.Current));
                i++;
            }

            if (IsVerbose)
                Console.WriteLine(" : {0} genomes were successfully generated.", PopulationSize);
        }

        protected override Network Decode(IGenome<int> genome)
        {
            return new Network(((SubnetGenome)genome).Ids.Select(x => new Gene(x, _statistics[x])), _bypass);
        }

        protected override bool IsConverged()
        {
            //var fitnessNorm = Elites.RootMeanSquareNorm(x => x.Fitness);
            //var diff = (fitnessNorm - _prevFitnessNorm) / _prevFitnessNorm;
            
            //if (diff < Tolerance) _s++;
            //else _s = 0;

            //_prevFitnessNorm = fitnessNorm;

            var fitnessNorm = Elites.RootMeanSquareNorm(x => x.Fitness);
            var diff = (fitnessNorm - _prevFitnessNorm) / _prevFitnessNorm;

            if (diff < Tolerance) _s++;
            else _s = 0;

            _prevFitnessNorm = fitnessNorm;

            return _s == StationarySolution;
        }

        protected override bool IsAcceptable(List<IGenome<int>> population, IGenome<int> genome)
        {
            return _forcing
                       ? ((SubnetGenome) genome).Ids.Count >= _minNodes &&
                         ((SubnetGenome) genome).Ids.Any(_bypass.Contains) &&
                         base.IsAcceptable(population, genome)
                       : ((SubnetGenome) genome).Ids.Count >= _minNodes &&
                         base.IsAcceptable(population, genome);
        }

        #endregion 
    }
}
